package com.bonc.ioc.demo.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 自动脱敏工具
 */
public class DesensUtil {
    private static final Pattern numberPattern = Pattern.compile("^[-+]?[\\d]*$");
    private static final Pattern haveNumberPattern = Pattern.compile(".*\\d+.*");
    private static final Pattern haveChinesePattern = Pattern.compile("[\u4e00-\u9fa5]");
    private static final Pattern haveEnglishPattern = Pattern.compile("[a-zA-z]");

    // 姓氏库
    private static final String nameDict = "赵|钱|孙|李|周|吴|郑|王|冯|陈|褚|卫|蒋|沈|韩|杨|朱|秦|尤|许|何|吕|施|张|孔|曹|严|华|金|魏|陶|姜|戚|谢|邹|喻|柏|水|窦|章|云|苏|潘|葛|奚|范|彭|郎|鲁|韦|昌|马|苗|凤|花|方|俞|任|袁|柳|酆|鲍|史|唐|费|廉|岑|薛|雷|贺|倪|汤|滕|殷|罗|毕|郝|邬|安|常|乐|于|时|傅|皮|卞|齐|康|伍|余|元|卜|顾|孟|平|黄|和|穆|萧|尹|姚|邵|湛|汪|祁|毛|禹|狄|米|贝|明|臧|计|伏|成|戴|谈|宋|茅|庞|熊|纪|舒|屈|项|祝|董|梁|杜|阮|蓝|闵|席|季|麻|强|贾|路|娄|危|江|童|颜|郭|梅|盛|林|刁|锺|徐|邱|骆|高|夏|蔡|田|樊|胡|凌|霍|虞|万|支|柯|昝|管|卢|莫|经|房|裘|缪|干|解|应|宗|丁|宣|贲|邓|郁|单|杭|洪|包|诸|左|石|崔|吉|钮|龚|程|嵇|邢|滑|裴|陆|荣|翁|荀|羊|於|惠|甄|麴|家|封|芮|羿|储|靳|汲|邴|糜|松|井|段|富|巫|乌|焦|巴|弓|牧|隗|山|谷|车|侯|宓|蓬|全|郗|班|仰|秋|仲|伊|宫|宁|仇|栾|暴|甘|钭|历|戎|祖|武|符|刘|景|詹|束|龙|叶|幸|司|韶|郜|黎|蓟|溥|印|宿|白|怀|蒲|邰|从|鄂|索|咸|籍|赖|卓|蔺|屠|蒙|池|乔|阳|郁|胥|能|苍|双|闻|莘|党|翟|谭|贡|劳|逄|姬|申|扶|堵|冉|宰|郦|雍|却|璩|桑|桂|濮|牛|寿|通|边|扈|燕|冀|僪|浦|尚|农|温|别|庄|晏|柴|瞿|阎|充|慕|连|茹|习|宦|艾|鱼|容|向|古|易|慎|戈|廖|庾|终|暨|居|衡|步|都|耿|满|弘|匡|国|文|寇|广|禄|阙|东|欧|殳|沃|利|蔚|越|夔|隆|师|巩|厍|聂|晁|勾|敖|融|冷|訾|辛|阚|那|简|饶|空|曾|毋|沙|乜|养|鞠|须|丰|巢|关|蒯|相|查|后|荆|红|游|竺|权|逮|盍|益|桓|公|万俟|司马|上官|欧阳|夏侯|诸葛|闻人|东方|赫连|皇甫|尉迟|公羊|澹台|公冶|宗政|濮阳|淳于|单于|太叔|申屠|公孙|仲孙|轩辕|令狐|钟离|宇文|长孙|慕容|司徒|司空|召|有|舜|叶赫那拉|丛|岳|寸|贰|皇|侨|彤|竭|端|赫|实|甫|集|象|翠|狂|辟|典|良|函|芒|苦|其|京|中|夕|之|章佳|那拉|冠|宾|香|果|依尔根觉罗|依尔觉罗|萨嘛喇|赫舍里|额尔德特|萨克达|钮祜禄|他塔喇|喜塔腊|讷殷富察|叶赫那兰|库雅喇|瓜尔佳|舒穆禄|爱新觉罗|索绰络|纳喇|乌雅|范姜|碧鲁|张廖|张简|图门|太史|公叔|乌孙|完颜|马佳|佟佳|富察|费莫|蹇|称|诺|来|多|繁|戊|朴|回|毓|税|荤|靖|绪|愈|硕|牢|买|但|巧|枚|撒|泰|秘|亥|绍|以|壬|森|斋|释|奕|姒|朋|求|羽|用|占|真|穰|翦|闾|漆|贵|代|贯|旁|崇|栋|告|休|褒|谏|锐|皋|闳|在|歧|禾|示|是|委|钊|频|嬴|呼|大|威|昂|律|冒|保|系|抄|定|化|莱|校|么|抗|祢|綦|悟|宏|功|庚|务|敏|捷|拱|兆|丑|丙|畅|苟|随|类|卯|俟|友|答|乙|允|甲|留|尾|佼|玄|乘|裔|延|植|环|矫|赛|昔|侍|度|旷|遇|偶|前|由|咎|塞|敛|受|泷|袭|衅|叔|圣|御|夫|仆|镇|藩|邸|府|掌|首|员|焉|戏|可|智|尔|凭|悉|进|笃|厚|仁|业|肇|资|合|仍|九|衷|哀|刑|俎|仵|圭|夷|徭|蛮|汗|孛|乾|帖|罕|洛|淦|洋|邶|郸|郯|邗|邛|剑|虢|隋|蒿|茆|菅|苌|树|桐|锁|钟|机|盘|铎|斛|玉|线|针|箕|庹|绳|磨|蒉|瓮|弭|刀|疏|牵|浑|恽|势|世|仝|同|蚁|止|戢|睢|冼|种|涂|肖|己|泣|潜|卷|脱|谬|蹉|赧|浮|顿|说|次|错|念|夙|斯|完|丹|表|聊|源|姓|吾|寻|展|出|不|户|闭|才|无|书|学|愚|本|性|雪|霜|烟|寒|少|字|桥|板|斐|独|千|诗|嘉|扬|善|揭|祈|析|赤|紫|青|柔|刚|奇|拜|佛|陀|弥|阿|素|长|僧|隐|仙|隽|宇|祭|酒|淡|塔|琦|闪|始|星|南|天|接|波|碧|速|禚|腾|潮|镜|似|澄|潭|謇|纵|渠|奈|风|春|濯|沐|茂|英|兰|檀|藤|枝|检|生|折|登|驹|骑|貊|虎|肥|鹿|雀|野|禽|飞|节|宜|鲜|粟|栗|豆|帛|官|布|衣|藏|宝|钞|银|门|盈|庆|喜|及|普|建|营|巨|望|希|道|载|声|漫|犁|力|贸|勤|革|改|兴|亓|睦|修|信|闽|北|守|坚|勇|汉|练|尉|士|旅|五|令|将|旗|军|行|奉|敬|恭|仪|母|堂|丘|义|礼|慈|孝|理|伦|卿|问|永|辉|位|让|尧|依|犹|介|承|市|所|苑|杞|剧|第|零|谌|招|续|达|忻|六|鄞|战|迟|候|宛|励|粘|萨|邝|覃|辜|初|楼|城|区|局|台|原|考|妫|纳|泉|老|清|德|卑|过|麦|曲|竹|百|福|言|第五|佟|爱|年|笪|谯|哈|墨|南宫|赏|伯|佴|佘|牟|商|西门|东门|左丘|梁丘|琴|后|况|亢|缑|帅|微生|羊舌|海|归|呼延|南门|东郭|百里|钦|鄢|汝|法|闫|楚|晋|谷梁|宰父|夹谷|拓跋|壤驷|乐正|漆雕|公西|巫马|端木|颛孙|子车|督|仉|司寇|亓官|鲜于|锺离|盖|逯|库|郏|逢|阴|薄|厉|稽|闾丘|公良|段干|开|光|操|瑞|眭|泥|运|摩|伟|铁|迮";
    private static Map<String, String> nameMap = new HashMap<>();
    // 地址关键性排序
    private static final String addSort = "区|县|市|省";
    private static List<String> addList = new ArrayList<>();

    static {
        desensRuleLoad();
    }

    /**
     * 加载脱敏知识库
     */
    private static void desensRuleLoad() {
        // 姓氏库加载
        String[] names = DesensUtil.nameDict.split("\\|", -1);
        Map<String, String> map = new HashMap<>();
        for (String singleName : names) {
            map.put(singleName, "");
        }
        DesensUtil.nameMap = map;

        // 地址关键性加载
        String[] addArr = addSort.split("\\|", -1);
        DesensUtil.addList = new ArrayList<>(Arrays.asList(addArr));
    }

    /**
     * 脱敏——电话号
     */
    public String transDesensTel(String string) {
        return DesensUtil.strUtil(string, 3, 7);
    }

    /**
     * 脱敏——固定电话
     */
    public String transDesensSeat(String string) {
        return DesensUtil.strUtil(string, 4, 8);
    }

    /**
     * 脱敏——身份证号
     */
    public String transDesensID(String string) {
        return DesensUtil.strUtil(string, 3, 15);
    }

    /**
     * 脱敏——驾驶证号
     */
    public String transDesensDriver(String string) {
        return DesensUtil.strUtil(string, 4, 8);
    }

    /**
     * 脱敏——车牌号
     */
    public String transDesensPlate(String string) {
        return DesensUtil.strUtil(string, 2, 5);
    }

    /**
     * 脱敏——姓名
     * 复姓优先规则
     */
    public String transDesensName(String string) {
        return DesensUtil.nameDesensUtil(string);
    }

    /**
     * 脱敏——地址
     */
    public String transDesensAddress(String string) {
        return DesensUtil.addressDesensUtil(string);
    }

    /**
     * 智能脱敏函数（自动判断需要脱敏的是什么类型的数据）
     */
    public String transDesensAuto(String string) {
        String result = "";

        if (string.length() == 11 && DesensUtil.isInteger(string)) {
            // 十一位并且为纯数字 --> 手机号
            result = transDesensTel(string);
        } else if (string.length() == 8 && DesensUtil.isInteger(string)) {
            // 八位并且为纯数字 --> 座机号
            result = transDesensSeat(string);
        } else if (string.length() == 18 && DesensUtil.isInteger(string.substring(0, 17))) {
            // 十八位并且前十七位为纯数字 --> 身份证号
            result = transDesensID(string);
        } else if (string.length() == 12 && DesensUtil.isInteger(string)) {
            // 十二位纯数字 --> 驾驶证号
            result = transDesensDriver(string);
        } else if (string.length() == 7 && DesensUtil.hasDigit(string) && DesensUtil.hasChinese(string) && DesensUtil.hasEnglish(string)) {
            // 七位且有中文，有英文字母，有数字 --> 车牌号
            result = transDesensPlate(string);
        } else {
            // 判断是地址还是姓名
            String kind = DesensUtil.judgmentAddOrName(string);
            switch (kind) {
                case "add":
                    // 地址
                    result = transDesensAddress(string);
                    break;
                case "name":
                    // 人名
                    result = transDesensName(string);
                    break;
                default:
                    // 无法判断按地址算
                    result = transDesensAddress(string);
                    break;
            }
        }

        return result;
    }

    //==================================================================================================================

    /**
     * 脱敏函数字符串处理工具
     */
    private static String strUtil(String str, int startIndex, int endIndex) {

        String beforeStr = str.substring(0, startIndex);
        String afterStr = str.substring(endIndex, str.length());

        StringBuilder betweenStrBuilder = new StringBuilder();
        for (int i = 0; i < endIndex - startIndex; i++) {
            betweenStrBuilder.append("*");
        }
        String betweenStr = betweenStrBuilder.toString();

        return beforeStr + betweenStr + afterStr;
    }

    /**
     * 姓名脱敏工具
     */
    private static String nameDesensUtil(String name) {

        int n = getNameCapIndex(name);
        return strUtil(name, n, name.length());
    }

    /**
     * 查找姓氏所在索引范围（入口）
     */
    private static int getNameCapIndex(String name) {

        return getNameCapIndex(name, name.length() - 1);
    }

    /**
     * 查找姓氏所在索引范围
     */
    private static int getNameCapIndex(String name, int index) {
        // 如果未找到该姓氏，以第一个字为姓
        if (index == 0) {
            return 1;
        }
        // 获取猜测姓氏
        String nameCap = name.substring(0, index);
        // 是否在姓氏规则库中
        boolean flag = nameMap.containsKey(nameCap);
        if (flag) {
            // 找到姓氏
            return index;
        } else {
            // 继续查找 (index - 1)
            return getNameCapIndex(name, index - 1);
        }
    }

    /**
     * MD5 转换
     */
    public static String getMd5Str(String str) {

        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        char[] chars = str.toCharArray();
        byte[] bytes = new byte[chars.length];

        for (int i = 0, chars_len = chars.length; i < chars_len; i++) {
            bytes[i] = (byte) chars[i];
        }

        assert md5 != null;
        byte[] md5Bytes = md5.digest(bytes);
        StringBuilder stringBuffer = new StringBuilder();

        for (byte md5Byte : md5Bytes) {
            int val = ((int) md5Byte) & 0xff;
            if (val < 16) {
                stringBuffer.append("0");
            }
            stringBuffer.append(Integer.toHexString(val));
        }
        return stringBuffer.toString();
    }

    /**
     * 寻找一个字或一个词在一段字符串中的索引
     * string : 原字符串
     * searchStr : 搜索字符串
     * leftPriority : 是否左侧优先（true : 左侧优先 false : 右侧优先）
     */
    private static int getIndexStr(String string, String searchStr) {
        int searchLength = searchStr.length();
        String[] str = cutStringToArr(string, searchLength);

        int index = -1;
        for (int i = 0, len = str.length; i < len; i++) {
            if (str[i].equals(searchStr)) {
                index = i;
                break;
            }
        }
        return index;
    }

    /**
     * 将字符串拆成字符串数组
     */
    private static String[] cutStringToArr(String string, int n) {

        String[] str = string.split("");

        String[] newStr = new String[str.length + 1 - n];

        for (int i = 0; i < str.length + 1 - n; i++) {
            StringBuilder stringBuilder = new StringBuilder();
            for (int j = 0; j < n; j++) {
                stringBuilder.append(str[i + j]);
            }
            newStr[i] = stringBuilder.toString();
        }
        return newStr;
    }

    /**
     * 地址脱敏工具
     */
    private static String addressDesensUtil(String address) {

        String overDesecs = "";
        int num = 0;
        for (String keyAdd : DesensUtil.addList) {
            int index = getIndexStr(address, keyAdd);
            if (!(index < 0 || index == address.length() - 1)) {
                // ! (未找到关键字或该关键字为最详细单位 --> 需要查找下一关键字)
                overDesecs = strUtil(address, index + 1, address.length());
                num++;
                break;
            }
        }
        if (num == 0) {
            // 未成功脱敏
            overDesecs = strUtil(address, 0, address.length());
        }
        return overDesecs;
    }

    // =================================================================================================================
    // 字符串操作

    /**
     * 判断字符串是否为纯数字
     */
    private static boolean isInteger(String str) {
        return numberPattern.matcher(str).matches();
    }

    /**
     * 判断字符是否为汉字
     */
    private static boolean isChineseChar(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
                || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
                || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS;
    }

    /**
     * 判断字符串是否为纯中文
     */
    private static boolean isChinese(String name) {
        boolean res = true;
        char[] cTemp = name.toCharArray();
        for (int i = 0; i < name.length(); i++) {
            if (!isChineseChar(cTemp[i])) {
                res = false;
                break;
            }
        }
        return res;
    }

    /**
     * 判断字符串是否包含数字
     */
    private static boolean hasDigit(String str) {
        return haveNumberPattern.matcher(str).find();
    }

    /**
     * 判断字符串是否包含中文
     */
    private static boolean hasChinese(String str) {
        return haveChinesePattern.matcher(str).find();
    }

    /**
     * 判断字符串是否包含英文字母（大小写都包括）
     */
    private static boolean hasEnglish(String str) {
        return haveEnglishPattern.matcher(str).find();
    }

    /**
     * 判断是地址还是姓名
     */
    private static String judgmentAddOrName(String string) {

        String kind;

        if (!isChinese(string)) {
            // 如果不为纯汉字则必然不是人名
            kind = "add";
        } else if (string.length() > 20) {
            // 如果长度大于20则必然不是人名
            kind = "add";
        } else if (string.contains("省") && string.contains("市") && getIndexStr(string, "省") < getIndexStr(string, "市")) {
            // 如果既有省又有市，并且省在市前面则比为地址
            kind = "add";
        } else {
            kind = "name";
        }
        return kind;
    }

    public static void main(String[] args) {

        DesensUtil desensUtil = new DesensUtil();
        System.out.println(desensUtil.transDesensAuto("23010219960226161X"));

    }

}
